#include "Precompile.h"
#include "DX11RenderSystem.h"
#include "DX11MaterialManager.h"
#include "DX11Texture2D.h"

namespace Graphics{
	DX11RenderSystem::DX11RenderSystem(RenderMode &mode):
		m_currentRasterizerState(NULL),RenderSystem(mode),m_context(NULL){
			m_deviceHandle = NULL;
			
			// Create render context
			m_renderContext = boost::shared_ptr<DX11RenderContext>(new DX11RenderContext);
			boost::static_pointer_cast<DX11RenderContext>(m_renderContext)->m_renderSystem = this;

			// Create material manager
			m_materialManager = boost::shared_ptr<MaterialManager>(new DX11MaterialManager);
	}

	DX11RenderSystem::~DX11RenderSystem(){
		RenderSystem::cleanup();
		cleanup();
	}

	void DX11RenderSystem::initialize(HWND hwnd){
		m_hWnd = hwnd;
		resetRenderSystem(m_renderMode);
		m_frameTimer->restart();
		m_loadedAndReady = true;
	}

	/// Sets up the render system according to the render mode parameter.
	void DX11RenderSystem::resetRenderSystem(RenderMode &mode){
		cleanup();
		m_renderMode = mode;

		UINT deviceFlags = 0;
#ifdef _DEBUG
		deviceFlags = D3D11_CREATE_DEVICE_DEBUG;  
#endif
		DXGI_SWAP_CHAIN_DESC swapDesc;
		swapDesc.BufferCount = 1;
	    swapDesc.BufferDesc.Width = m_renderMode.resolutionXY.first;
	    swapDesc.BufferDesc.Height = m_renderMode.resolutionXY.second;
	    swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	    swapDesc.BufferDesc.RefreshRate.Numerator = 60;
	    swapDesc.BufferDesc.RefreshRate.Denominator = 1;
	    swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	    swapDesc.OutputWindow = m_hWnd;
	    swapDesc.SampleDesc.Count = 1;
	    swapDesc.SampleDesc.Quality = 0;
		swapDesc.SwapEffect = DXGI_SWAP_EFFECT_SEQUENTIAL;
		swapDesc.Windowed = !m_renderMode.fullscreen;
		swapDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

		// Create device
		if(FAILED(D3D11CreateDeviceAndSwapChain(NULL,D3D_DRIVER_TYPE_HARDWARE,NULL,deviceFlags,NULL,0,D3D11_SDK_VERSION,&swapDesc,&m_swapChain,&m_deviceHandle,&m_featureLevel,&m_context))){
			// Error handling
			LOG_ERROR("DX11RenderSystem::resetRenderSystem - Failed to create graphics device and or swap chain.");
			assert(0);
		}

		setRasterizerState();
		
		// Set up render context
		boost::shared_ptr<DX11RenderContext> renderContext = boost::static_pointer_cast<DX11RenderContext>(m_renderContext);
		renderContext->m_context = m_context;
		renderContext->m_handle = m_deviceHandle;
		renderContext->m_materialManager = m_materialManager.get();
		renderContext->m_animationManager = m_animationManager.get();

		RenderSystem::resetDevice();
		m_materialManager->initialize(m_renderContext.get());

		int multisampleCount = Core::GeneralSettings::getMultisampleCount();
		// Create multisampled render target
		if(multisampleCount > 1)
			m_multisampledRenderTarget = GraphicsFactory::createRenderTarget(
											GraphicsFactory::createEmptyTexture2D(	m_renderMode.resolutionXY.first,
																					m_renderMode.resolutionXY.second,
																					multisampleCount,
																					Texture2D::SRS_NONE,
																					TF_R8G8B8A8_UNORM));

		// Create a back buffer
	    ID3D11Texture2D *pBackBuffer;
	    m_swapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (LPVOID*)&pBackBuffer );
		m_renderTarget = GraphicsFactory::createRenderTarget(Texture2DPtr(new DX11Texture2D(pBackBuffer,
																							m_renderMode.resolutionXY.first,
																							m_renderMode.resolutionXY.second,
																							1,
																							TF_R8G8B8A8_UNORM,
																							m_renderContext.get())));

		m_renderTarget->setRenderTarget(true,true,m_renderContext.get());

		setViewport();

		// Default topology
		m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	}

	void DX11RenderSystem::saveRenderStates(){
		m_renderStates = DX11RenderStatesPtr(new DX11RenderStates);
		m_context->IAGetInputLayout(&m_renderStates->m_inputLayout);
		m_context->OMGetDepthStencilState(&m_renderStates->m_DSstate,&m_renderStates->m_ref);
		m_context->RSGetState(&m_renderStates->m_rState);
		m_context->VSGetShader(&m_renderStates->m_vShader,&m_renderStates->m_vClsInst,&m_renderStates->m_vNumClsInst);
		m_context->PSGetShader(&m_renderStates->m_pShader,&m_renderStates->m_pClsInst,&m_renderStates->m_pNumClsInst);
		m_context->VSGetConstantBuffers(0,1,&m_renderStates->m_cBuffer);
		m_context->PSGetSamplers(0,1,&m_renderStates->m_sState);
	}

	bool DX11RenderSystem::restoreRenderStates(){
		if(m_renderStates){
			m_context->OMSetDepthStencilState(m_renderStates->m_DSstate,m_renderStates->m_ref);
			m_context->IASetInputLayout(m_renderStates->m_inputLayout);
			m_context->RSSetState(m_renderStates->m_rState);
			m_context->PSSetShader(m_renderStates->m_pShader,&m_renderStates->m_pClsInst,m_renderStates->m_pNumClsInst);
			m_context->VSSetShader(m_renderStates->m_vShader,&m_renderStates->m_vClsInst,m_renderStates->m_vNumClsInst);
			m_context->VSSetConstantBuffers(0,1,&m_renderStates->m_cBuffer);
			m_context->PSSetSamplers(0,1,&m_renderStates->m_sState);
			m_renderStates.reset();
			return true;
		}
		return false;
	}

	ID3D11DeviceContext* DX11RenderSystem::createDefferedContext(){
		ID3D11DeviceContext* context = NULL;
		HRESULT hr = m_deviceHandle->CreateDeferredContext(0,&context);
		if(FAILED(hr)){
			LOG_ERROR("Failed in creating deffered context.");
			assert(0);
		}
		return context;
	}

	void DX11RenderSystem::beginFrame(){
		bool clearRT = false;
		if(!m_SkySphere){
			clearRT = true;
		}

		// Set the multisampled render targets.
		if(m_multisampledRenderTarget)
			m_multisampledRenderTarget->setRenderTarget(clearRT,true,m_renderContext.get());
		else
			m_renderTarget->clear(clearRT,true,m_renderContext.get());
	}

	void DX11RenderSystem::endFrame(){
		if(m_multisampledRenderTarget){
			// Copy it over because we can't resolve on present
			m_multisampledRenderTarget->resolveIntoRenderTarget(m_renderTarget,m_renderContext.get());

			// Use original render targets again
			m_renderTarget->setRenderTarget(false,false,m_renderContext.get());
		}

		// Render GUI
		m_guiMutex->lock();
		std::set<GUIManagerPtr>::iterator guiItr = m_GUIManagers.begin();
		while(guiItr != m_GUIManagers.end()){
			(*guiItr)->render(m_renderContext);
			guiItr++;
		}
		m_guiMutex->unlock();

		PostRenderEvent.invoke();

		// Present rendered image
		m_swapChain->Present(m_renderMode.vsync,0);
	}

	void DX11RenderSystem::drawIndexed(UINT IndexCount,UINT StartIndexLocation,INT BaseVertexLocation){
		m_context->DrawIndexed(IndexCount,StartIndexLocation,BaseVertexLocation);
	}

	void DX11RenderSystem::draw(UINT numVerts, UINT startLoc){
		m_context->Draw(numVerts,startLoc);
	}

	/// Cleanup rendersystem resources and devices
	void DX11RenderSystem::cleanup(){
		if(m_context){
			m_context->OMSetRenderTargets(0,NULL,NULL);
			m_context->VSSetShader(NULL,NULL,0);
			m_context->PSSetShader(NULL,NULL,0);
			m_context->CSSetShader(NULL,NULL,0);
		}

		if(m_multisampledRenderTarget)
			m_multisampledRenderTarget.reset();
		if(m_renderTarget)
			m_renderTarget.reset();

		if(m_currentRasterizerState && m_currentRasterizerState->Release() != 0){
			LOG_ERROR("Reference count fault.");
			assert(0);
		}
		if(m_context){
			m_context->ClearState();
			m_context->Flush();
			if(m_context->Release() != 0){
				LOG_ERROR("Reference count fault.");
				assert(0);
			}
		}
		if(m_deviceHandle){
			while(m_deviceHandle->Release() != 0){}
		}
	}

	/// Gets translated multisample value
	UINT DX11RenderSystem::getSampleCount(){
		switch(m_renderMode.multisampling){
		case MS_1X:
			return 1;
		case MS_2X:
			return 2;
		case MS_4X:
			return 4;
		case MS_8X:
			return 8;
		case MS_16X:
			return 16;
		}
		return 1;
	}

	///	Sets the viewport from the current rendermode.
	void DX11RenderSystem::setViewport(){
		D3D11_VIEWPORT vp;
	    vp.Width = (FLOAT)m_renderMode.resolutionXY.first;
	    vp.Height = (FLOAT)m_renderMode.resolutionXY.second;
	    vp.MinDepth = 0.0f;
	    vp.MaxDepth = 1.0f;
	    vp.TopLeftX = 0;
	    vp.TopLeftY = 0;
	    m_context->RSSetViewports( 1, &vp );
	}

	///	Sets the rasterizer state from the current rendermode.
	void DX11RenderSystem::setRasterizerState(){
	    D3D11_RASTERIZER_DESC CurrentRasterizerState;
	    CurrentRasterizerState.FillMode = D3D11_FILL_SOLID;
		CurrentRasterizerState.CullMode = D3D11_CULL_BACK;
	    CurrentRasterizerState.FrontCounterClockwise = false;
	    CurrentRasterizerState.DepthBias = false;
	    CurrentRasterizerState.DepthBiasClamp = 0;
	    CurrentRasterizerState.SlopeScaledDepthBias = 0;
	    CurrentRasterizerState.DepthClipEnable = true;
	    CurrentRasterizerState.ScissorEnable = false;
		if(m_renderMode.multisampling != MS_1X)
	    	CurrentRasterizerState.MultisampleEnable = true;
		else
			CurrentRasterizerState.MultisampleEnable = false;
	    CurrentRasterizerState.AntialiasedLineEnable = false;

		ID3D11RasterizerState* currentState = NULL;
		if(FAILED(m_deviceHandle->CreateRasterizerState(&CurrentRasterizerState, &currentState))){
			// Error handling
			LOG_ERROR("DX11RenderSystem::setRasterizerState - Failed to create rasterizer state.");
			assert(0);
		}
	    m_context->RSSetState(currentState);

		if(m_currentRasterizerState)
			m_currentRasterizerState->Release();
		m_currentRasterizerState = currentState;
	}
}